#include "PersonSerializer.h"
#include <xercesc/dom/DOM.hpp>
#include <xercesc/framework/MemBufFormatTarget.hpp>
#include "common/Base64.h"
#include "common/XmlStringTranscoder.h"
#include "common/DateSerializer.h"

namespace etu {
namespace genealogy_tree {

using etu::common::Base64;
using etu::common::DateSerializer;

XERCES_CPP_NAMESPACE_USE

std::string PersonSerializer::serialize(const Person* person) {
    DOMDocument* doc = this->createDocument("person");
    DOMElement* root = doc->getDocumentElement();

    root->setAttribute(X("first_name"), X(person->getFirstName()));
    root->setAttribute(X("last_name"), X(person->getLastName()));
    root->setAttribute(X("gender"), X(this->genderToString(person->getGender())));

    if(!person->getPhotoId().empty()) {
        root->setAttribute(X("photo_id"), X(person->getPhotoId()));
    }

    DateSerializer dateSerializer;

    DOMElement* birthDateNode = doc->createElement(X("birth_date"));
    Date* birthDate = (Date *) &person->getBirthDate();
    birthDateNode->setTextContent(X(dateSerializer.serialize(birthDate)));
    root->appendChild(birthDateNode);

    DOMElement* deathDateNode = doc->createElement(X("death_date"));
    Date* deathDate = (Date *) &person->getDeathDate();
    deathDateNode->setTextContent(X(dateSerializer.serialize(deathDate)));
    root->appendChild(deathDateNode);

    if(!person->getDescription().empty()) {
        DOMElement* descNode = doc->createElement(X("description"));
        descNode->setTextContent(X(person->getDescription()));
        root->appendChild(descNode);
    }

    std::string ret = this->documentToString(doc);
    doc->release();
    
    return Base64::encode(ret);
}

Person* PersonSerializer::deserialize(const std::string& src) throw(ParseException) {
    DOMDocument* doc = this->buildDocument(Base64::decode(src));
    DOMElement* root = doc->getDocumentElement();

    if(!root->hasAttribute(X("first_name"))) {
        throw ParseException("Корень должен содержать атрибут 'first_name'");
    }

    if(!root->hasAttribute(X("last_name"))) {
        throw ParseException("Корень должен содержать атрибут 'last_name'");
    }

    if(!root->hasAttribute(X("gender"))) {
        throw ParseException("Корень должен содержать атрибут 'gender'");
    }

    Gender gender =
        this->stringToGender(XMLString::transcode(root->getAttribute(X("gender"))));

    Date* birthDate = NULL;
    Date* deathDate = NULL;
    std::string description = "";
    DateSerializer dateSerializer;
    std::string firstName = XMLString::transcode(root->getAttribute(X("first_name")));
    std::string lastName = XMLString::transcode(root->getAttribute(X("last_name")));
    std::string photoId = "";

    if(root->hasAttribute(X("photo_id"))) {
        photoId = XMLString::transcode(root->getAttribute(X("photo_id")));
    }

    DOMNodeList* childs = root->getChildNodes();
    const XMLSize_t dateNodesCount = childs->getLength();

    for(XMLSize_t i = 0; i < dateNodesCount; i++) {
        DOMNode* node = childs->item(i);

        if((node->getNodeType() != 0)
                && node->getNodeType() == DOMNode::ELEMENT_NODE) {

            DOMElement* childNode = dynamic_cast<DOMElement* >(node);
            std::string nodeName = XMLString::transcode(childNode->getNodeName());

            if(nodeName == "birth_date") {
                birthDate = dateSerializer.deserialize(
                        XMLString::transcode(childNode->getTextContent()));
            } else if(nodeName == "death_date") {
                deathDate = dateSerializer.deserialize(
                        XMLString::transcode(childNode->getTextContent()));
            } else if(nodeName == "description") {
                description = XMLString::transcode(childNode->getTextContent());
            }
        }
    }

    if(birthDate == NULL) {
        throw ParseException("Не найдена информация о дате рождения человека");
    }

    if(deathDate == NULL) {
        throw ParseException("Не найдена информация о дате смерти человека");
    }

    Person* ret = new Person(firstName, lastName, gender, *birthDate,
            *deathDate, description, photoId);

    delete birthDate;
    delete deathDate;
    doc->release();

    return ret;
}

Gender PersonSerializer::stringToGender(const std::string& str) {
    Gender ret;

    if(str == "male") {
        ret = MALE_GENDER;
    } else if(str == "female") {
        ret = FEMALE_GENDER;
    } else {
        ret = UNKNOWN_GENDER;
    }

    return ret;
}

std::string PersonSerializer::genderToString(const Gender& gender) {
    std::string ret;

    switch(gender) {
        case MALE_GENDER:
            ret = "male";
            break;
        case FEMALE_GENDER:
            ret = "female";
            break;
        default:
            ret = "unknown";
    }

    return ret;
}

}
}
